package util;


import java.io.Serializable;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.Query;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Eine Klasse, die die meisten ben&ouml;tigten Funktionen, die man zur
 * Kommunikation mit der Datenbank ben&ouml;tigt, bereitstellt.
 * 
 * @author J&ouml;rg M&uuml;hle
 */
public class GenericHibernateDAO implements IGenericDAO {
	protected static final Log log = LogFactory
			.getLog(GenericHibernateDAO.class);

	private int maxResults = 0;

	private int offset = 0;

	/*
	 * (non-Javadoc)
	 * 
	 * @see anno.core.dao.IGenericDAO#findByLikeAttributes(java.lang.Class,
	 *      java.lang.String[], java.lang.Object[])
	 */
	@SuppressWarnings("unchecked")
	public <TEntity> List<TEntity> findByWhereAttributes(
			Class<TEntity> entityClass, String[] attributeNames,
			String[] attributeValues) throws RuntimeException {
		return findByWhereAttributes(entityClass, attributeNames,
				attributeValues, null, true);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see anno.core.dao.IGenericDAO#findByLikeAttributes(java.lang.Class,
	 *      java.lang.String[], java.lang.Object[], java.lang.String, boolean)
	 */
	@SuppressWarnings("unchecked")
	public <TEntity> List<TEntity> findByWhereAttributes(
			Class<TEntity> entityClass, String[] attributeNames,
			String[] attributeValues, String orderByProperty,
			boolean sortDirectionAsc) throws RuntimeException {
		log.debug("finding all " + entityClass.getName() + " by attributes.");
		try {
			EntityManager em = HibernateHelper.getInstance().getEntityManager();
			String qryStr = "from " + entityClass.getName() + " entity";

			// where clause
			if (attributeNames != null && attributeValues != null) {
				qryStr += prepareWhereClause(qryStr, attributeNames,
						attributeValues);
			}

			// sort order
			if (orderByProperty != null && orderByProperty.length() > 0)
				qryStr += " order by entity." + orderByProperty
						+ (sortDirectionAsc ? " asc" : " desc");

			// limit result (max and offset)
			Query query = em.createQuery(qryStr);
			if (maxResults > 0)
				query.setMaxResults(maxResults);
			if (offset > 0)
				query.setFirstResult(offset);

			List<TEntity> results = (List<TEntity>) query.getResultList();
			log.debug("find all " + entityClass.getName()
					+ " by attributes successful, result size: "
					+ results.size());
			return results;
		} catch (RuntimeException re) {
			log.error("find all " + entityClass.getName()
					+ " by attributes failed", re);
			throw re;
		}
	}

	/**
	 * L&auml;dt eine Instanz einer Klasse durch Angabe des Prim&auml;ren
	 * Schl&uuml;ssels.
	 * 
	 * @param entityClass
	 *            Die Klasse die zu laden ist.
	 * @param key
	 *            Der prim&auml;re SChl&uuml;ssel der gesuchten Instanz.
	 * @param lockMode
	 *            Der Lockmode
	 * @return Die Instanz oder null, wenn keine gefunden wurde.
	 */
	@SuppressWarnings("unchecked")
	public <TEntity, TKey extends Serializable> TEntity get(
			Class<TEntity> entityClass, TKey key, LockModeType lockModeType)
			throws RuntimeException {
		log.debug("getting " + entityClass.getName() + " instance with key: "
				+ key);
		try {
			EntityManager em = HibernateHelper.getInstance().getEntityManager();
			TEntity instance = null;
			if (lockModeType != null) {
				em.lock(entityClass, lockModeType);
			}
			instance = (TEntity) em.find(entityClass, key);
			if (instance == null) {
				log.debug("get successful, no instance found");
			} else {
				log.debug("get successful, instance found");
			}
			return instance;
		} catch (RuntimeException re) {
			log.error("get " + entityClass.getName() + " failed", re);
			throw re;
		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see anno.core.dao.IGenericDAO#getByKey(java.lang.Class, TKey)
	 */
	public <TEntity, TKey extends Serializable> TEntity getByKey(
			Class<TEntity> entityClass, TKey key) throws RuntimeException {
		return get(entityClass, key, null);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see anno.core.dao.IGenericDAO#getCountLike(java.lang.Class,
	 *      java.lang.String[], java.lang.Object[])
	 */
	public <TEntity> int getCountWhereAttributes(Class<TEntity> entityClass,
			String[] attributeNames, String[] attributeValues) {
		log.debug("getting count for " + entityClass.getName()
				+ " by attributes.");
		try {
			EntityManager em = HibernateHelper.getInstance().getEntityManager();

			String qryStr = "select count(entity) from "
					+ entityClass.getName() + " entity";

			// where clause
			if (attributeNames != null && attributeValues != null) {
				qryStr += prepareWhereClause(qryStr, attributeNames,
						attributeValues);
			}

			Query query = em.createQuery(qryStr);
			int count = ((Long) query.getResultList().get(0)).intValue();
			log.debug("getting count for " + entityClass.getName()
					+ " by attributes successful, count: " + count);
			return count;
		} catch (RuntimeException re) {
			log.error("getting count for " + entityClass.getName()
					+ " by attributes failed", re);
			throw re;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see anno.core.dao.IGenericDAO#makePersistent(TEntity)
	 */
	@SuppressWarnings("unchecked")
	public <TEntity> TEntity makePersistent(TEntity detachedEntity)
			throws RuntimeException {
		log.debug("making " + detachedEntity.getClass().getName()
				+ " instance persistent.");
		TEntity attachedEntity = null;
		try {
			EntityManager em = HibernateHelper.getInstance().getEntityManager();
			attachedEntity = (TEntity) em.merge(detachedEntity);
			log.debug("make persistent successful");
		} catch (RuntimeException re) {
			log.error("make persistent failed", re);
			throw re;
		}
		return attachedEntity;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see anno.core.dao.IGenericDAO#makeTransient(java.lang.Object)
	 */
	public void makeTransient(Object entity) {
		log.debug("remove persistent instance " + entity.getClass().getName());
		try {
			EntityManager em = HibernateHelper.getInstance().getEntityManager();
			em.remove(entity);
		} catch (RuntimeException re) {
			log.error("remove persistent instance failed", re);
			throw re;
		}
	}

	/**
	 * Soll SQL injection verhindern. Prüft ob der Wert nur ein Wert ist und
	 * keine anderen sql anweisungen enthält.
	 * 
	 * @param param
	 * @return
	 */
	private boolean isSQLParamValid(String param) {
		if (param == null)
			return false;

		boolean b1 = param.contains("'");
		boolean b2 = param.contains(",");
		boolean b3 = param.contains(";");
		boolean b4 = param.contains("--");
		boolean b5 = param.contains("select");
		boolean b6 = param.contains("insert");
		boolean b7 = param.contains("update");
		boolean b8 = param.contains("xp_");

		return !(b1 || b2 || b3 || b4 || b5 || b6 || b7 || b8);
	}

	private String prepareWhereClause(String qryStr, String[] attributeNames,
			String[] attributeValues) {

		String clause = " where";

		for (int i = 0; i < attributeNames.length; i++) {
			String attributeName = attributeNames[i];
			String attributeValue = attributeValues[i];

			if (attributeName != null && isSQLParamValid(attributeValue)) {
				if (!clause.equals(" where"))
					clause += " and";

				// greater equal than
				if (attributeValue.startsWith(">=")) {
					clause += " entity." + attributeName + " >= '"
							+ attributeValue.substring(2) + "'";
				}
				// greater than
				else if (attributeValue.startsWith(">")) {
					clause += " entity." + attributeName + " > '"
							+ attributeValue.substring(1) + "'";
				}
				// lower equal than
				else if (attributeValue.startsWith("<=")) {
					clause += " entity." + attributeName + " <= '"
							+ attributeValue.substring(2) + "'";
				}
				// lower than
				else if (attributeValue.startsWith("<")) {
					clause += " entity." + attributeName + " < '"
							+ attributeValue.substring(1) + "'";
				}
				// equal
				else if (attributeValue.startsWith("=")) {
					clause += " entity." + attributeName + " = '"
							+ attributeValue.substring(1) + "'";
				}
				// like
				else {
					clause += " entity." + attributeName + " like '"
							+ attributeValue + "'";
				}
			}
		}
		if (clause.equals(" where"))
			return "";
		return clause;

	}
	
	@Override
	public int getMaxResults() {
		return maxResults;
	}
	
	@Override
	public int getOffset() {
		return offset;
	}
	
	@Override
	public void setMaxResults(int max) {
		this.maxResults = max;
	}
	
	@Override
	public void setOffset(int offset) {
		this.offset = offset;
	}
	
}
